package com.example.camunda.rest;

import cn.hutool.core.io.IoUtil;
import com.example.camunda.dto.ProcessDefinitionDto;
import com.example.camunda.dto.ProcessInstanceDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 流程定义
 *
 * @author heshao
 * @date 2021/11/8 17:25
 **/
@Api(tags = "一、流程定义")
@RestController
@RequestMapping("/process-definition")
public class ProcessDefinitionController {

    private final RepositoryService repositoryService;
    private final RuntimeService runtimeService;

    public ProcessDefinitionController(RepositoryService repositoryService, RuntimeService runtimeService) {
        this.repositoryService = repositoryService;
        this.runtimeService = runtimeService;
    }

    /**
     * 查询所有流程定义
     *
     * @param pageNum  分页页码
     * @param pageSize 分页大小
     * @return
     */
    @GetMapping
    @ApiOperation("查询流程定义")
    public Page<ProcessDefinitionDto> list(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery().latestVersion();
        long count = query.count();
        List<ProcessDefinition> list = query.listPage(pageNum * pageSize, pageSize);
        List<ProcessDefinitionDto> dtoList = list.stream().map(ProcessDefinitionDto::from).collect(Collectors.toList());
        return new PageImpl<>(dtoList, PageRequest.of(pageNum, pageSize), count);
    }

    /**
     * 查询流程定义
     *
     * @param id 流程定义ID
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("查询流程定义")
    public ProcessDefinitionDto get(@PathVariable String id) {
        ProcessDefinition definition = repositoryService.getProcessDefinition(id);
        return ProcessDefinitionDto.from(definition);
    }

    /**
     * 删除流程定义
     *
     * @param id 流程定义ID
     * @return
     */
    @DeleteMapping("/{id}")
    @ApiOperation("删除流程定义")
    public void delete(@PathVariable String id) {
        repositoryService.deleteProcessDefinition(id);
    }

    /**
     * 查询流程图
     *
     * @param id 流程定义ID
     * @return
     */
    @GetMapping("/{id}/diagram")
    @ApiOperation("查询流程图")
    public void getDiagram(@PathVariable String id, HttpServletResponse response) {
        InputStream in = repositoryService.getProcessDiagram(id);
        if (in == null) {
            return;
        }
        ProcessDefinition definition = repositoryService.getProcessDefinition(id);
        getDiagram(response, in, definition.getDiagramResourceName());
    }

    /**
     * 查询流程图xml
     *
     * @param id 流程定义ID
     * @return
     */
    @GetMapping("/{id}/xml")
    @ApiOperation("查询流程图xml")
    public String getBpmn20Xml(@PathVariable String id) {
        InputStream in = repositoryService.getProcessModel(id);
        return IoUtil.read(in, "UTF-8");
    }

    /**
     * 激活/挂起流程定义
     *
     * @param id 流程定义ID
     * @return
     */
    @PutMapping("/{id}/suspended")
    @ApiOperation("激活/挂起流程定义")
    public void suspended(@PathVariable String id) {
        ProcessDefinition definition = repositoryService.getProcessDefinition(id);
        if (definition.isSuspended()) {
            repositoryService.activateProcessDefinitionById(id);
        } else {
            repositoryService.suspendProcessDefinitionById(id);
        }
    }

    /**
     * 启动流程实例
     *
     * @param id          流程定义ID
     * @param businessKey 业务主键
     * @return
     */
    @PostMapping("/{id}/start")
    @ApiOperation("启动流程实例")
    public ProcessInstanceDto start(@PathVariable String id, String businessKey) {
        ProcessInstance instance = runtimeService.createProcessInstanceById(id)
                .businessKey(businessKey)
                .execute();
        return ProcessInstanceDto.from(instance);
    }

    /**
     * 重启流程实例
     *
     * <p>重新启动同步取消、终止或已完成的流程实例</p>
     *
     * @param id 流程实例ID
     * @return
     */
    @PostMapping("/{id}/restart")
    @ApiOperation("重启流程实例")
    public void start(@PathVariable String id) {
        ProcessInstance instance = runtimeService.createProcessInstanceById(id).execute();
        runtimeService.restartProcessInstances(instance.getProcessDefinitionId())
                .processInstanceIds(Collections.singletonList(id))
                .execute();
    }

    /**
     * 异步重启流程实例
     * <p>重新启动同步取消、终止或已完成的流程实例</p>
     *
     * @param id 流程定义ID
     * @return
     */
    @PostMapping("/{id}/restart-async")
    @ApiOperation("异步重启流程实例")
    public void startAsync(@PathVariable String id) {
        ProcessInstance instance = runtimeService.createProcessInstanceById(id).execute();
        runtimeService.restartProcessInstances(instance.getProcessDefinitionId())
                .processInstanceIds(Collections.singletonList(id))
                .executeAsync();
    }

    /**
     * 查询流程定义
     *
     * @param key 流程定义key
     * @return
     */
    @GetMapping("/key/{key}")
    @ApiOperation("查询流程定义")
    public ProcessDefinitionDto getByKey(@PathVariable String key) {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .withoutTenantId()
                .latestVersion()
                .singleResult();
        return ProcessDefinitionDto.from(definition);
    }

    /**
     * 删除流程定义
     *
     * @param key 流程定义key
     * @return
     */
    @DeleteMapping("/key/{key}")
    @ApiOperation("删除流程定义")
    public void deleteByKey(@PathVariable String key) {
        repositoryService.deleteProcessDefinitions().byKey(key).delete();
    }

    /**
     * 查询流程图
     *
     * @param key 流程定义key
     * @return
     */
    @GetMapping("/key/{key}/diagram")
    @ApiOperation("查询流程图")
    public void getDiagramByKey(@PathVariable String key, HttpServletResponse response) {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .withoutTenantId()
                .latestVersion()
                .singleResult();
        InputStream in = repositoryService.getProcessDiagram(definition.getId());
        if (in == null) {
            return;
        }
        getDiagram(response, in, definition.getDiagramResourceName());
    }

    /**
     * 查询流程图xml
     *
     * @param key 流程定义key
     * @return
     */
    @GetMapping("/key/{key}/xml")
    @ApiOperation("查询流程图xml")
    public String getBpmn20XmlByKey(@PathVariable String key) {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .withoutTenantId()
                .latestVersion()
                .singleResult();
        InputStream in = repositoryService.getProcessModel(definition.getId());
        return IoUtil.read(in, "UTF-8");
    }

    /**
     * 激活/挂起流程定义
     *
     * @param key 流程定义key
     * @return
     */
    @PutMapping("/key/{key}/suspended")
    @ApiOperation("激活/挂起流程定义")
    public void suspendedByKey(@PathVariable String key) {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(key)
                .withoutTenantId()
                .latestVersion()
                .singleResult();

        String id = definition.getId();
        if (definition.isSuspended()) {
            repositoryService.activateProcessDefinitionById(id);
        } else {
            repositoryService.suspendProcessDefinitionById(id);
        }
    }

    /**
     * 启动流程实例
     *
     * @param key         流程定义key
     * @param businessKey 业务主键
     * @return
     */
    @PostMapping("/key/{key}/start")
    @ApiOperation("启动流程实例")
    public ProcessInstanceDto startByKey(@PathVariable String key, String businessKey) {
        ProcessInstance instance = runtimeService.createProcessInstanceByKey(key)
                .businessKey(businessKey)
                .execute();
        return ProcessInstanceDto.from(instance);
    }

    @SneakyThrows
    private void getDiagram(HttpServletResponse response, InputStream in, String filename) {
        response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");

        OutputStream out = response.getOutputStream();
        IoUtil.copy(in, out);

        IoUtil.close(in);

        IoUtil.flush(out);
        IoUtil.close(out);
    }


}
